/*-------------------------------------------------------------------------
 *
 * win32security.c
 *	  Microsoft Windows Win32 安全支持函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/port/win32security.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif


/*
 * 前端和后端报告错误信息的工具包装器。
 */
static
pg_attribute_printf(1, 2)
void log_error(const char *fmt,...)
{
	va_list		ap;

	va_start(ap, fmt);
#ifndef FRONTEND
	write_stderr(fmt, ap);
#else
	fprintf(stderr, fmt, ap);
#endif
	va_end(ap);
}

/*
 * 如果当前用户具有管理权限，则返回非零值；
 * 如果没有，则返回零。
 *
 * 注意：因为在启动期间调用得太早，所以无法使用 ereport()。
 */
int pgwin32_is_admin(void)
{
	PSID		AdministratorsSid;
	PSID		PowerUsersSid;
	SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
	BOOL		IsAdministrators;
	BOOL		IsPowerUsers;

	if (!AllocateAndInitializeSid(&NtAuthority, 2,
								  SECURITY_BUILTIN_DOMAIN_RID,
								  DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0,
								  0, &AdministratorsSid))
	{
		log_error(_("could not get SID for Administrators group: error code %lu\n"),
				  GetLastError());
		exit(1);
	}

	if (!AllocateAndInitializeSid(&NtAuthority, 2,
								  SECURITY_BUILTIN_DOMAIN_RID,
								  DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0,
								  0, &PowerUsersSid))
	{
		log_error(_("could not get SID for PowerUsers group: error code %lu\n"),
				  GetLastError());
		exit(1);
	}

	if (!CheckTokenMembership(NULL, AdministratorsSid, &IsAdministrators) ||
		!CheckTokenMembership(NULL, PowerUsersSid, &IsPowerUsers))
	{
		log_error(_("could not check access token membership: error code %lu\n"),
				  GetLastError());
		exit(1);
	}

	FreeSid(AdministratorsSid);
	FreeSid(PowerUsersSid);

	if (IsAdministrators || IsPowerUsers)
		return 1;
	else
		return 0;
}

/*
 * 如果以下任一情况为真，则我们认为自己作为服务在运行：
 *
 * 1) 标准错误无效（对服务而言始终如此，而以服务身份运行的 pg_ctl
 *	  将其传递给 postgres，参见 CreateRestrictedProcess()）
 * 2) 我们以 LocalSystem 身份运行（仅由服务使用）
 * 3) 我们的令牌包含 SECURITY_SERVICE_RID（服务启动时由 SCM 自动添加到
 *	  进程令牌中）
 *
 * 需要检查 LocalSystem，因为令人惊讶的是，如果服务以 LocalSystem 身份运行，
 * 它的进程令牌中不会包含 SECURITY_SERVICE_RID。
 *
 * 返回值：
 *	 0 = 不是服务
 *	 1 = 服务
 *	-1 = 错误
 *
 * 注意：我们无法通过 ereport 报告错误（因为我们在后端调用得太早）
 * 或 write_stderr（因为这会调用此功能）。因此，我们只能直接
 * 在 stderr 上写入，这很糟糕，但我们几乎没有其他选择。
 */
int pgwin32_is_service(void)
{
	static int	_is_service = -1;
	BOOL		IsMember;
	PSID		ServiceSid;
	PSID		LocalSystemSid;
	SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
	HANDLE		stderr_handle;

	/* 仅在第一次检查 */
	if (_is_service != -1)
		return _is_service;

	/* 检查标准错误是否无效 */
	stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
	if (stderr_handle != INVALID_HANDLE_VALUE && stderr_handle != NULL)
	{
		_is_service = 0;
		return _is_service;
	}

	/* 检查是否以 LocalSystem 身份运行 */
	if (!AllocateAndInitializeSid(&NtAuthority, 1,
								  SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0,
								  &LocalSystemSid))
	{
		fprintf(stderr, "could not get SID for local system account\n");
		return -1;
	}

	if (!CheckTokenMembership(NULL, LocalSystemSid, &IsMember))
	{
		fprintf(stderr, "could not check access token membership: error code %lu\n",
				GetLastError());
		FreeSid(LocalSystemSid);
		return -1;
	}
	FreeSid(LocalSystemSid);

	if (IsMember)
	{
		_is_service = 1;
		return _is_service;
	}

	/* 检查服务组成员资格 */
	if (!AllocateAndInitializeSid(&NtAuthority, 1,
								  SECURITY_SERVICE_RID, 0, 0, 0, 0, 0, 0, 0,
								  &ServiceSid))
	{
		fprintf(stderr, "could not get SID for service group: error code %lu\n",
				GetLastError());
		return -1;
	}

	if (!CheckTokenMembership(NULL, ServiceSid, &IsMember))
	{
		fprintf(stderr, "could not check access token membership: error code %lu\n",
				GetLastError());
		FreeSid(ServiceSid);
		return -1;
	}
	FreeSid(ServiceSid);

	if (IsMember)
		_is_service = 1;
	else
		_is_service = 0;

	return _is_service;
}
